/*-------------------------------------------------------------------------
 *
 * execScan.c
 *	  该代码提供对广义关系扫描的支持。ExecScan
 *	  被传递一个节点和一个指向“做正确的事”
 *	  的函数的指针，并从关系中返回一个元组。ExecScan
 *	  然后进行繁琐的工作 - 检查资格并适当地投影元组。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/executor/execScan.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "executor/executor.h"
#include "miscadmin.h"
#include "utils/memutils.h"



/*
 * ExecScanFetch -- 检查中断并获取下一个潜在的元组
 *
 * 该例程负责在我们处于 EvalPlanQual 重新检查时替换测试元组。如果我们不是，则只需执行访问方法的下一个元组例程。
 */
static inline TupleTableSlot *
fc_ExecScanFetch(ScanState *fc_node,
			  ExecScanAccessMtd fc_accessMtd,
			  ExecScanRecheckMtd fc_recheckMtd)
{
	EState	   *fc_estate = fc_node->ps.state;

	CHECK_FOR_INTERRUPTS();

	if (fc_estate->es_epq_active != NULL)
	{
		EPQState   *fc_epqstate = fc_estate->es_epq_active;

		/*
		 * 我们在 EvalPlanQual 重新检查中。如果可用，则返回测试元组，重新检查任何访问方法特定条件。
		 */
		Index		fc_scanrelid = ((Scan *) fc_node->ps.plan)->scanrelid;

		if (fc_scanrelid == 0)
		{
			/*
			 * 这是一个 ForeignScan 或 CustomScan，它将连接向远程端推送。重新检查方法不仅需要重新检查扫描/连接资格，还需要将正确的元组存储在槽中。
			 */

			TupleTableSlot *fc_slot = fc_node->ss_ScanTupleSlot;

			if (!(*fc_recheckMtd) (fc_node, fc_slot))
				ExecClearTuple(fc_slot);	/* 不会由扫描返回 */
			return fc_slot;
		}
		else if (fc_epqstate->relsubs_done[fc_scanrelid - 1])
		{
			/*
			 * 返回空槽，因为此关系没有 EPQ 元组或者我们已经返回了它。
			 */

			TupleTableSlot *fc_slot = fc_node->ss_ScanTupleSlot;

			return ExecClearTuple(fc_slot);
		}
		else if (fc_epqstate->relsubs_slot[fc_scanrelid - 1] != NULL)
		{
			/*
			 * 返回由 EPQ 调用者提供的替换元组。
			 */

			TupleTableSlot *fc_slot = fc_epqstate->relsubs_slot[fc_scanrelid - 1];

			Assert(fc_epqstate->relsubs_rowmark[fc_scanrelid - 1] == NULL);

			/* 标记以记住我们不应该再次返回它 */
			fc_epqstate->relsubs_done[fc_scanrelid - 1] = true;

			/* 如果我们还没有得到测试元组，则返回空槽 */
			if (TupIsNull(fc_slot))
				return NULL;

			/* 检查它是否满足访问方法条件 */
			if (!(*fc_recheckMtd) (fc_node, fc_slot))
				return ExecClearTuple(fc_slot);	/* 不会被
												 * 扫描返回 */
			return fc_slot;
		}
		else if (fc_epqstate->relsubs_rowmark[fc_scanrelid - 1] != NULL)
		{
			/*
			 * 使用非锁定行标记获取并返回替换元组。
			 */

			TupleTableSlot *fc_slot = fc_node->ss_ScanTupleSlot;

			/* 标记以记住我们不应该返回更多 */
			fc_epqstate->relsubs_done[fc_scanrelid - 1] = true;

			if (!EvalPlanQualFetchRowMark(fc_epqstate, fc_scanrelid, fc_slot))
				return NULL;

			/* 如果我们还没有得到测试元组，则返回空槽 */
			if (TupIsNull(fc_slot))
				return NULL;

			/* 检查它是否满足访问方法条件 */
			if (!(*fc_recheckMtd) (fc_node, fc_slot))
				return ExecClearTuple(fc_slot);	/* 不会被
												 * 扫描返回 */
			return fc_slot;
		}
	}

	/*
	 * 运行特定于节点类型的访问方法函数来获取下一个元组
	 */
	return (*fc_accessMtd) (fc_node);
}

/* ----------------------------------------------------------------
 *		ExecScan
 *
 *		使用指示的“访问方法”扫描关系并返回下一个合格元组。
 *		访问方法返回下一个元组，ExecScan() 负责检查所返回的元组是否符合条件子句。
 *
 *		必须提供一个“重新检查方法”，该方法可以检查关系的任意元组与访问方法内部实现的任何条件。
 *
 *		条件：
 *		  -- AMI 维护的“游标”位于之前返回的元组处。
 *
 *		初始状态：
 *		  -- 指定的关系已打开以进行扫描，因此“游标”位于第一个合格元组之前。
 * ----------------------------------------------------------------
 */
TupleTableSlot * ExecScan(ScanState *fc_node,
		 ExecScanAccessMtd fc_accessMtd,	/* 返回元组的功能 */
		 ExecScanRecheckMtd fc_recheckMtd)
{
	ExprContext *fc_econtext;
	ExprState  *fc_qual;
	ProjectionInfo *fc_projInfo;

	/*
	 * 从节点获取数据
	 */
	fc_qual = fc_node->ps.qual;
	fc_projInfo = fc_node->ps.ps_ProjInfo;
	fc_econtext = fc_node->ps.ps_ExprContext;

	/* 中断检查在 ExecScanFetch 中 */

	/*
	 * 如果我们既没有要检查的条件，也没有要投影的内容，直接跳过
	 * 所有开销并返回原始扫描元组。
	 */
	if (!fc_qual && !fc_projInfo)
	{
		ResetExprContext(fc_econtext);
		return fc_ExecScanFetch(fc_node, fc_accessMtd, fc_recheckMtd);
	}

	/*
	 * 重置每个元组的内存上下文，以释放在上一个元组周期中分配的任何表达式评估
	 * 存储。
	 */
	ResetExprContext(fc_econtext);

	/*
	 * 从访问方法获取一个元组。 循环直到我们获得一个
	 * 通过资格的元组。
	 */
	for (;;)
	{
		TupleTableSlot *fc_slot;

		fc_slot = fc_ExecScanFetch(fc_node, fc_accessMtd, fc_recheckMtd);

		/*
		 * 如果访问方法返回的槽包含 NULL，则意味着
		 * 没有更多内容可扫描，因此我们只返回一个空槽，
		 * 小心使用投影结果槽，以便它具有正确的
		 * tupleDesc。
		 */
		if (TupIsNull(fc_slot))
		{
			if (fc_projInfo)
				return ExecClearTuple(fc_projInfo->pi_state.resultslot);
			else
				return fc_slot;
		}

		/*
		 * 将当前元组放入表达式上下文
		 */
		fc_econtext->ecxt_scantuple = fc_slot;

		/*
		 * 检查当前元组是否满足条件子句
		 *
		 * 此处检查非空条件以避免调用 ExecQual()
		 * 当条件为空时... 仅节省几个周期，但它们累积
		 * ...
		 */
		if (fc_qual == NULL || ExecQual(fc_qual, fc_econtext))
		{
			/*
			 * 找到了一个满意的扫描元组。
			 */
			if (fc_projInfo)
			{
				/*
				 * 形成一个投影元组，将其存储在结果元组槽中
				 * 并返回它。
				 */
				return ExecProject(fc_projInfo);
			}
			else
			{
				/*
				 * 在这里，我们没有进行投影，所以直接返回扫描元组。
				 */
				return fc_slot;
			}
		}
		else
			InstrCountFiltered1(fc_node, 1);

		/*
		 * 元组未通过条件，所以释放每个元组的内存并重试。
		 */
		ResetExprContext(fc_econtext);
	}
}

/*
 * ExecAssignScanProjectionInfo
 *		为扫描节点设置投影信息（如果需要）。
 *
 * 如果请求的 tlist 与底层元组类型完全匹配，
 * 我们可以避免投影步骤。 如果是这样，我们只需将 ps_ProjInfo 设置为 NULL。
 * 请注意，这种情况不仅发生在简单的“SELECT * FROM ...”中，
 * 在大多数情况下，当存在连接或其他处理节点在
 * 扫描节点之上时也会发生，因为规划器将优先生成匹配的
 * tlist。
 *
 * 扫描槽的描述符必须已经设置。
 */
void ExecAssignScanProjectionInfo(ScanState *fc_node)
{
	Scan	   *fc_scan = (Scan *) fc_node->ps.plan;
	TupleDesc	fc_tupdesc = fc_node->ss_ScanTupleSlot->tts_tupleDescriptor;

	ExecConditionalAssignProjectionInfo(&fc_node->ps, fc_tupdesc, fc_scan->scanrelid);
}

/*
 * ExecAssignScanProjectionInfoWithVarno
 *		与上述相同，但调用者可以指定在 tlist 中的 Vars 中期望的 varno。
 */
void ExecAssignScanProjectionInfoWithVarno(ScanState *fc_node, int fc_varno)
{
	TupleDesc	fc_tupdesc = fc_node->ss_ScanTupleSlot->tts_tupleDescriptor;

	ExecConditionalAssignProjectionInfo(&fc_node->ps, fc_tupdesc, fc_varno);
}

/*
 * ExecScanReScan
 *
 * 此函数必须在任何使用 ExecScan() 的计划节点类型的 ReScan 函数内调用。
 */
void ExecScanReScan(ScanState *fc_node)
{
	EState	   *fc_estate = fc_node->ps.state;

	/*
	 * 我们必须清除扫描元组，以便观察者（例如 execCurrent.c）
	 * 可以知道这个计划节点未定位在元组上。
	 */
	ExecClearTuple(fc_node->ss_ScanTupleSlot);

	/*
	 * 如果我们在 EvalPlanQual 重新检查内部，重新扫描 EvalPlanQual 元组。
	 * 但不要丢失被阻塞目标关系的“阻塞”状态。
	 */
	if (fc_estate->es_epq_active != NULL)
	{
		EPQState   *fc_epqstate = fc_estate->es_epq_active;
		Index		fc_scanrelid = ((Scan *) fc_node->ps.plan)->scanrelid;

		if (fc_scanrelid > 0)
			fc_epqstate->relsubs_done[fc_scanrelid - 1] =
				fc_epqstate->epqExtra->relsubs_blocked[fc_scanrelid - 1];
		else
		{
			Bitmapset  *fc_relids;
			int			fc_rtindex = -1;

			/*
			 * 如果 FDW 或自定义扫描提供者已将连接替换为
			 * 扫描，则会有多个 RTI；重置所有 RTI 的 epqScanDone 标志。
			 */
			if (IsA(fc_node->ps.plan, ForeignScan))
				fc_relids = ((ForeignScan *) fc_node->ps.plan)->fs_relids;
			else if (IsA(fc_node->ps.plan, CustomScan))
				fc_relids = ((CustomScan *) fc_node->ps.plan)->custom_relids;
			else
				elog(ERROR, "unexpected scan node: %d",
					 (int) nodeTag(fc_node->ps.plan));

			while ((fc_rtindex = bms_next_member(fc_relids, fc_rtindex)) >= 0)
			{
				Assert(fc_rtindex > 0);
				fc_epqstate->relsubs_done[fc_rtindex - 1] =
					fc_epqstate->epqExtra->relsubs_blocked[fc_rtindex - 1];
			}
		}
	}
}
